﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.Validation;
using Demo.Model.DatabaseContext;


namespace Demo.Repository
{
    public class DemoDAL<T> : IDemoDAL<T>
         where T : class
    {
        DbContext _dbContext = null;

        DbCommand _dbCommand = null;

        public DemoDAL()
        {
            _dbContext = new ProductContext();

        }

        public ICollection<T> GetAll(Expression<Func<T, bool>> match = null, string[] includes = null)
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = _dbContext.Set<T>().Include(includes.First()).Where(match);
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.ToList();
            }

            return _dbContext.Set<T>().Where(match).ToList();
        }

        public ICollection<T> GetAll(string[] includes = null)
        {
            if (includes != null && includes.Count() > 0)
            {
                var query = _dbContext.Set<T>().Include(includes.First());
                foreach (var include in includes.Skip(1))
                    query = query.Include(include);
                return query.ToList();
            }

            return _dbContext.Set<T>().ToList();
        }

        public async Task<ICollection<T>> GetAllAsync()
        {
            return await _dbContext.Set<T>().ToListAsync();
        }

        public T Get(int id)
        {
            return _dbContext.Set<T>().Find(id);
        }

        public async Task<T> GetAsync(int id)
        {
            return await _dbContext.Set<T>().FindAsync(id);
        }

        public T Find(Expression<Func<T, bool>> match)
        {
            return _dbContext.Set<T>().SingleOrDefault(match);
        }

        public async Task<T> FindAsync(Expression<Func<T, bool>> match)
        {
            return await _dbContext.Set<T>().SingleOrDefaultAsync(match);
        }

        public ICollection<T> FindAll(Expression<Func<T, bool>> match)
        {
            return _dbContext.Set<T>().Where(match).ToList();
        }

        public async Task<ICollection<T>> FindAllAsync(Expression<Func<T, bool>> match)
        {
            return await _dbContext.Set<T>().Where(match).ToListAsync();
        }

        public T Add(T t, bool vaild = true)
        {
            _dbContext.Set<T>().Add(t);
            _dbContext.Configuration.ValidateOnSaveEnabled = vaild;
            _dbContext.SaveChanges();
            return t;
        }

        public async Task<T> AddAsync(T t)
        {
            _dbContext.Set<T>().Add(t);
            await _dbContext.SaveChangesAsync();
            return t;
        }

        public T Update(T updated, int key)
        {
            if (updated == null)
                return null;

            T existing = _dbContext.Set<T>().Find(key);

            if (existing != null)
            {
                _dbContext.Entry(existing).CurrentValues.SetValues(updated);
                _dbContext.SaveChanges();
            }

            return existing;
        }

        public T Update(T t)
        {
            if (t == null)
                return null;

            _dbContext.Set<T>().Attach(t);
            _dbContext.Entry(t).State = EntityState.Modified;

            _dbContext.SaveChanges();


            return t;
        }

        public async Task<T> UpdateAsync(T updated, int key)
        {
            if (updated == null)
                return null;

            T existing = await _dbContext.Set<T>().FindAsync(key);
            if (existing != null)
            {
                _dbContext.Entry(existing).CurrentValues.SetValues(updated);
                await _dbContext.SaveChangesAsync();
            }
            return existing;
        }

        public void Delete(T t)
        {

            _dbContext.Set<T>().Attach(t);
            _dbContext.Entry(t).State = EntityState.Deleted;
            _dbContext.SaveChanges();
        }

        public async Task<int> DeleteAsync(T t)
        {
            _dbContext.Set<T>().Attach(t);
            _dbContext.Entry(t).State = EntityState.Deleted;
            return await _dbContext.SaveChangesAsync();
        }

        public int Count()
        {
            return _dbContext.Set<T>().Count();
        }

        public async Task<int> CountAsync()
        {
            return await _dbContext.Set<T>().CountAsync();
        }

        public List<T> MapToList<T>(DbDataReader dr) where T : new()
        {
            try
            {
                if (dr != null && dr.HasRows)
                {
                    var entity = typeof(T);
                    var entities = new List<T>();
                    var propDict = new Dictionary<string, PropertyInfo>();
                    var props = entity.GetProperties(BindingFlags.Instance | BindingFlags.Public);
                    propDict = props.ToDictionary(p => p.Name.ToUpper(), p => p);

                    while (dr.Read())
                    {
                        T newObject = new T();
                        for (int index = 0; index < dr.FieldCount; index++)
                        {
                            if (propDict.ContainsKey(dr.GetName(index).ToUpper()))
                            {
                                var info = propDict[dr.GetName(index).ToUpper()];
                                if ((info != null) && info.CanWrite)
                                {
                                    var val = dr.GetValue(index);
                                    info.SetValue(newObject, (val == DBNull.Value) ? null : val, null);
                                }
                            }
                        }
                        entities.Add(newObject);
                    }
                    return entities;
                }
                return null;
            }
            catch
            {
                return null;
            }
            finally
            {
                if (_dbContext.Database.Connection.State == System.Data.ConnectionState.Open)
                {
                    _dbContext.Database.Connection.Close();
                }
            }
        }

        public ICollection<T> CallProcedure(string procName, System.Data.SqlClient.SqlParameter[] parameter)
        {

            return _dbContext.Database.SqlQuery<T>(procName, parameter).ToList();
        }

        public DbDataReader CallProcedure(string procName, SqlParameter[] parameter, string nothing)
        {
            try
            {
                //if(_dbContext.Database.Connection.State == System.Data.ConnectionState.Open)
                _dbContext.Database.Connection.Open();
                _dbCommand = _dbContext.Database.Connection.CreateCommand();

                _dbCommand.CommandText = procName;
                _dbCommand.CommandType = System.Data.CommandType.StoredProcedure;

                for (int iCount = 0; iCount != parameter.Length; iCount++)
                {
                    _dbCommand.Parameters.Add(new SqlParameter(parameter[iCount].ParameterName, parameter[iCount].Value));
                }


                return _dbCommand.ExecuteReader();
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {

            }
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }
}
